<?php
/*******************************************************************************

    FinalsClub.org is a platform for students and faculty to collaborate
    and share their knowledge with the world.

    Copyright (C) 2009  Andrew Magliozzi
 
    This file is a part of FinalsClub.org.

    FinalsClub.org is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
 
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
    To contact FinalsClub.org, send an email to info@finalsclub.org, or
    send mail to Finals Club, c/o Veritas Tutors, 1132 Massachusetts Avenue,
    Cambridge, MA 02138.

*******************************************************************************/

class Note extends BaseNote
{
	
	/**
	 * Holds the current versioning state (on/off)
	 * @author CROSCON
	 */
	protected $doVersioning = true;

	/**
	 * Constructor. Attaches revision control behavior to the model.
	 * @author CROSCON
	 */
	public function __construct() {
		parent::__construct();
		sfPropelBehavior::add('Note', array(
			'versionable' => array(
				'columns' => array(
					'title' => 'topic',
					'version' => 'version'
				),
				'conditional' => 'versioning'
			)
		));
	}

	/**
	 * Get a note by version number.
	 * @param int $id The version ID number.
	 * @author CROSCON
	 */
	public function retrieveByVersion($version) {
		$this->toVersion($version);
		return $this;
	}

	/**
	 * Put a previous version life.
	 * @param int $version_id The version ID number.
	 * @param int $user_id Optional. The user ID to whom to attribute the change.
	 * @return boolean Returns false on failure.
	 * @author CROSCON
	 */
	public function changeLiveByVersion($version_id, $user_id=null) {
		if($user_id === null)
			$user_id = $this->getUserId();

		$note = new Note();
		$note->toVersion($version_id);
		$note->setVersionComment("Reverted to version {$version_id}");
		$note->setUserId($user_id);
		$note->setDeleted(false);
		return $note->save();
	}

	/**
	 * Toggle the delete flag on a version.
	 * @param int $version The version number.
	 * @param boolean Returns false on failure.
	 * @return boolean Returns false on failure.
	 * @author CROSCON
	 */
	public function versionDelete($version,$user_id=null,$comment=null) {

		$note_id = $this->getId();

		$result = NotePeer::toggleDeleteByVersion($note_id,$version,true);

		if(!$result)
			return false;

		$last = $this->getLastResourceVersion();

		if($last->getNumber() != $version)
			return true;

		$version_id = $this->getPreviousLiveVersionNumber();

		if(!$version_id)
			return false;

		$note = NotePeer::retrieveByPk($this->getId());
		$note->toVersion($version_id);
		if($comment===null)
			$note->setVersionComment(sprintf("Restored from revision #%d",$version_id));
		else
			$note->setVersionComment(sprintf("Restored from revision #%d - %s",$version_id,$comment));

		if($user_id===null)
			$user_id = $this->getUserId();

		$note->setVersionCreatedBy($user_id);

		$result = $note->save();
		if($result===false)
			return false;

		return true;
	}

	/**
	 * Untoggle the delete flag on a version.
	 * @param int $version The version number.
	 * @param boolean Returns false on failure.
	 * @author CROSCON
	 */
	public function versionUndelete($version) {
		$note_id = $this->getId();
		return NotePeer::toggleDeleteByVersion($note_id,$version,'');
	}

	/**
	 * Turns versioning on/off for subsequent operations, or, if called without $state parameter, returns current state.
	 * @param $state boolean Optional. If present, changes current versioning state. If not, returns current versioning state.
	 * @return boolean Current versioning state for the model.
	 * @author CROSCON
	 */
	public function versioning($state=null) {
		if($state!==null)
			$this->doVersioning = ($state) ? true : false;
		return $this->doVersioning;
	}

	/**
	 * Get the previous version number from the current note ID.
	 * @return mixed Returns false on failure, otherwise version.
	 * @author CROSCON
	 */
	public function getPreviousVersionNumber() {
		$version = $this->getLastResourceVersion()->getNumber();
		if($version===false)
			return false;
		--$version;
		if($version<1) return false;
		return $version;
	}

	/**
	 * Determine whether this Note is at terminal version; i.e. there is only one non-deleted version, therefore no more can be deleted.
	 * @return boolean True is yes, false if no.
	 * @author CROSCON
	 */
	public function isTerminalVersion() {
		$notes = $this->getAllVersionsSafe();
		$versionCount = 0;

		if(empty($notes[0])) return true;

		foreach($notes as $note) {
			//echo "<pre>\n"; var_dump($note); exit(0);
			if(!$note->getDeleted())
				$versionCount++;
		}

		if($versionCount == 1)
			return true;
		else
			return false;
	}

	/**
	 * Retrieve version last -1
	 * @return mixed Returns false on failure, otherswise note.
	 * @author CROSCON
	 */
	public function getPreviousVersion() {
		$last_id = $this->getPreviousVersionId();
		$note = new Note();

		if(!$last_id)
			return false;

		$note->setId($this->getId());
		$note->toVersion($last_id);
		return $note;
	}

	/**
	 * Get the last non-deleted version (used in deleting live items)
	 * @return mixed Returns false on failure, otherswise version number.
	 * @author CROSCON
	 */
	public function getPreviousLiveVersionNumber() {
		return $this->getPreviousLiveVersion(true);
	}

	/**
	 * Retrieve previous non-deleted version
	 * @param boolean $only_id If false, returns note object. If true, returns version ID.
	 * @return mixed Returns false on failure, and either version number of note, depending on 'only_id' input option.
	 * @author CROSCON
	 */
	public function getPreviousLiveVersion($only_id = false) {
		$last_id = $this->getPreviousVersionNumber();
		$note = new Note();

		if(!$last_id)
			return false;

		$note->setId($this->getId());
		$valid = false;

		while(!$valid) {
			$note->toVersion($last_id);
			if(!$note->getDeleted())
				$valid = true;
			if(!$valid) {
				if($last_id == 1)
					return false;
				else
					--$last_id;
			}
		}

		if($only_id)
			return $last_id;
		else
			return $note;
	}


	/**
	 * Version of getAllVersions safed for non-versioned data.
	 * @author CROSCON
	 */
	public function getAllVersionsSafe() {
		$pkid = $this->getId();
		$data = $this->getAllVersions();
		if(empty($data[0])) {
			$this->createVersionHistory(null,$pkid);
			return $this->getAllVersions();
		}
		return $data;
	}

	/**
	 * Create a versioning history for a Note that does not yet have one.
	 * @param PDOConnection $conn Connection to use, or NULL for default.
	 * @param int $note_id The note ID number.
	 * @return mixed boolean Returns true on success, false on failure.
	 * @author CROSCON
	 */
	public function createVersionHistory($conn=null,$note_id) {
		if($conn===null)
			$conn = Propel::getConnection(NotePeer::DATABASE_NAME, Propel::CONNECTION_WRITE);

		$note = NotePeer::retrieveByPk($note_id);
		$note_data = $note->toArray();

		$sql = "INSERT INTO resource_attribute_version (attribute_name,attribute_value) VALUES (?,?)";
		$stmt = $conn->prepare($sql);

		$resource_attribute_version_ids = array();

		foreach($note_data as $k=>$v) {
			if($k=='Version')
				$v = '1';
			$stmt->execute(array($k,$v));
			$resource_attribute_version_ids[] = $conn->lastInsertId();
		}

		$sql = "INSERT INTO resource_version (resource_id,resource_name,number,title,comment,created_by,created_at) VALUES (?,?,?,?,?,?,?)";
		$stmt = $conn->prepare($sql);
		$stmt->execute(array($note_id,'Note',1,$note_data['Topic'],'No comment',$note_data['UserId'],$note_data['CreatedAt']));
		$resource_version_id = $conn->lastInsertId();


		$sql = "INSERT INTO resource_attribute_version_hash (resource_attribute_version_id,resource_version_id,is_modified) VALUES(?,?,?)";
		$stmt = $conn->prepare($sql);
		foreach($resource_attribute_version_ids as $resource_attribute_version_id) {
			$stmt->execute(array($resource_attribute_version_id,$resource_version_id,1));
		}

		return true;
	}

	/* CLH 01/29/1009 */
	public function getHlTopic(){
		return link_to($this->getTopic(),'courses/show?id='.$this->getId());
	}
	
	public function save(PropelPDO $con = null,$user_id = null) {
		$activity_feed = new ActivityFeed();

		if($user_id===null) {
			$user = new User();
			$user = UserPeer::retrieveById($this->getUserId());		// note author
			$user_id = $user->getId();
		}

		$this->setVersionCreatedBy($user_id);

		$activity_feed->setUserId($user_id);		// set who wrote the note
		$activity_feed->setType(ActivityFeedPeer::NOTE_TYPE);				// set activity feed type (note)
		
		$visibility = $this->getVisibility();
		if($visibility != NotePeer::EVERYONE)	// if the note is not viewable to everyone, mark as private
			$activity_feed->setIspublic(false);
		else
			$activity_feed->setIspublic(true);
		$action = ($this->isNew()) ? "wrote" : "edited";	// new note or an edit?

		$message = "<strong>%s a course note</strong> for";	// the majority of the message gets set here
													// the rest of the message and links are generated by the renderNote helper method
		$formatted_message = sprintf($message, $action);
		$activity_feed->setMessage($formatted_message);	// set the message
		
		/* If a CourseSemester object does not yet exist linking the notes class to its semester, create it */
		$course_semester = CourseSemesterPeer::retrieveByCourseIdAndSemesterId($this->course_id, $this->semester_id);
		if(!$course_semester and $this->isNew()){
			$course_semester = new CourseSemester();
			$course_semester->setCourseId($this->course_id);
			$course_semester->setSemesterId($this->semester_id);
			$course_semester->save();
			
		}
		
		parent::save($con);		// we need to save the note first
		$this->updateLuceneIndex();  // update Lucene index
		
		$activity_feed->setTypeId($this->getId());		// set type id to the id of the note
		$activity_feed->save();
	}

	/**
	 * Removes this object from datastore and sets delete attribute.
	 * Overrides BaseNote delete
	 *
	 * @param      PropelPDO $con
	 * @return     void
	 * @throws     PropelException
	 * @see        BaseObject::setDeleted()
	 * @see        BaseObject::isDeleted()
	 * @see	   BaseNote::delete()
	 */
	public function delete(PropelPDO $con = null)
	{

		$index = Search::getLuceneIndex(Search::NOTE);
		$term  = new Zend_Search_Lucene_Index_Term($this->getId(), 'pk');
		$query = new Zend_Search_Lucene_Search_Query_Term($term);
		$hit   = $index->find($query);

		if ($hit) {
  			foreach($hit as $h) { $index->delete($h->id); }
		}

		parent::delete($con);
	}

  	/**
   	 * update against lucene index
   	 */
 	public function updateLuceneIndex() {
  		$index = Search::getLuceneIndex(Search::NOTE);
 
  		// remove an existing entry
		$term  = new Zend_Search_Lucene_Index_Term($this->getId(), 'pk');
		$query = new Zend_Search_Lucene_Search_Query_Term($term);
		$hit   = $index->find($query);
  		if ($hit) {
    			foreach($hit as $h) { $index->delete($h->id); }
  		}
 
  		$doc = new Zend_Search_Lucene_Document();
 
	  	// store job primary key URL to identify it in the search results
	  	$doc->addField(Zend_Search_Lucene_Field::Keyword('pk', $this->getId()));
 
	  	// index job fields
	  	$doc->addField(Zend_Search_Lucene_Field::Text('lecturer_name', $this->getLecturerName()));
	  	$doc->addField(Zend_Search_Lucene_Field::Text('topic', $this->getTopic()));
		$doc->addField(Zend_Search_Lucene_Field::UnIndexed('created',$this->getCreatedAt()));
	  	$doc->addField(Zend_Search_Lucene_Field::UnStored('text', $this->getText()));
		$doc->addField(Zend_Search_Lucene_Field::UnIndexed('course', $this->getCourse()->getName()));
		$doc->addField(Zend_Search_Lucene_Field::UnIndexed('course_id', $this->getCourseId()));
		$doc->addField(Zend_Search_Lucene_Field::Text('tags', $this->getTopic()));

  		// add job to the index
  		$index->addDocument($doc);
  		$index->commit();
  	}
  	
  	//return the number of times this note has been rated
  	public function getRatingCount()
  	{
  		return sizeof(RatingPeer::retrieveByTypeAndTypeId(RatingPeer::NOTE_TYPE, $this->getId()));
	
  	}
  	/*
  	 * TODO: This will not work, and needs to be integrated directly with the annotations materials versioning system
  	 * 
  	 * -Colin
  	public function setVisibility($v){
  		try{	
  			throw new Exception();
  		}catch(Exception $e){
  			echo $e->getTraceAsString();
  		}
  		$email = $this->getCourse()->getInstructorEmail();
  		if($this->visibility != NotePeer::EVERYONE && $v == NotePeer::EVERYONE && $email)
  			if(!SentSpamPeer::beenSpammedByEmail($this->getCourse()->getInstructorEmail())){
  				SentSpamPeer::getProfInvite($email,$this->getCourse()->getName());
  			}
  		parent::setVisibility($v);
  		
  	}*/

}
